﻿///Nokia QWERTY-Keboard Editor - tool for editing qwerty-keyboards on Symbian^1 - Symbian^3 devices
///Copyright (C) 2010-2012  Kalashnikov I.N.
///
///This program is free software; you can redistribute it and/or
///modify it under the terms of the GNU General Public License
///as published by the Free Software Foundation; either version 2
///of the License, or (at your option) any later version.
///
///This program is distributed in the hope that it will be useful,
///but WITHOUT ANY WARRANTY; without even the implied warranty of
///MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
///GNU General Public License for more details.
///
///You should have received a copy of the GNU General Public License
///along with this program; if not, write to the Free Software
///Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Resources;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows.Forms;

namespace Nokia_QWERTY_Keyboard_Editor
{
    public partial class MainForm : Form
    {
        private enum KeyboardFileType
        {
            VKB,
            SSQ,
            CN,
            Unknown
        }
        private enum LayerID : byte
        {
            CurrentLanguage = 0x01,
            CurrentLanguageShift = 0x02,
            English = 0x05,
            EnglishShift = 0x06,
            Numeric = 0x07,
            NumericShift = 0x08,
            IeroOne = 0x0b,
            IeroOneShift = 0x0c,
            IeroTwo = 0x0d,
            IeroTwoShift = 0x0e,
            IeroThree = 0x0f,
            IeroThreeShift = 0x10
        }
        private struct KeyaboardLayout
        {
            public string Lang;
            public string LangCode;
            public string Filepath;
            public byte[] Data;
            public List<int> LayersPosition;
            public List<Layer> Layers;
        }
        private struct Layer : IComparable<Layer>
        {
            public LayerID ID;
            public int Length;
            public int Width;
            public int Height;
            public int Index;
            public Symbol[] Symbols;

            public Layer(LayerID id, int index, int length, int width, int height, Symbol[] symbols)
            {
                this.ID = id;
                this.Length = length;
                this.Width = width;
                this.Height = height;
                this.Symbols = symbols;
                this.Index = index;
            }
            private Layer(Layer old, string[] symbols)
            {
                this.ID = old.ID;
                this.Length = old.Length;
                this.Width = old.Width;
                this.Height = old.Height;
                this.Index = old.Index;
                this.Symbols = null;
                this.Symbols = ApplyNewSymbols(old.Symbols, symbols);
            }
            public int CompareTo(Layer otherLayer)
            {
                return this.Index.CompareTo(otherLayer.Index);
            }
            public Layer CloneWithNewSymbols(string[] symbols)
            {
                return new Layer(this, symbols);
            }
            public override string ToString()
            {
                return string.Format("#{0} {1} ({2}x{3})", this.Index, this.ID.ToString(), this.Width, this.Height);
            }

            private Symbol[] ApplyNewSymbols(Symbol[] oldSyms, string[] newSyms)
            {
                var res = new Symbol[oldSyms.Length];
                for (int i = 0; i < res.Length; i++)
                    res[i] = new Symbol(newSyms[i], oldSyms[i].Position);
                return res;
            }
        }
        private struct Symbol
        {
            public string Char;
            public int Position;
            public bool IsEdited;

            public Symbol(string ch, int pos)
            {
                this.Char = ch;
                this.Position = pos;
                this.IsEdited = false;
            }
            public Symbol(Symbol s, string ch)
            {
                this.Char = ch;
                this.Position = s.Position;
                this.IsEdited = true;
            }

            public override string ToString()
            {
                return string.Format("{0} ({1})", Char, Position);
            }
        }

        #region Const
        private const string VKB_FILE = "peninputvkbwindowconfiginfo_*.rsc";
        private const string VKB_FORMAT = "{0}peninputvkbwindowconfiginfo_{1}.rsc";
        private const string VKB_REGEX = @"peninputvkbwindowconfiginfo_\d{2,3}\.rsc";
        private const string SSQ_FILE = "peninputssqwinconfiginfo_*.rsc";
        private const string SSQ_FORMAT = "{0}peninputssqwinconfiginfo_{1}.rsc";
        private const string SSQ_REGEX = @"peninputssqwinconfiginfo_\d{2,3}\.rsc";
        private const string CN_FILE = "peninputvkbcn.r*";
        private const string CN_FORMAT = "{0}peninputvkbcn.r{1}";
        private const string CN_REGEX = @"peninputvkbcn\.r\d{2,3}";
        #endregion
        #region Fields
        private KeyboardFileType kbType;
        private KeyaboardLayout layout;
        private ResourceManager resman = new ResourceManager(typeof(MainForm));
        private string folderpath;
        private List<Button> buttons;
        private List<int> codes;
        private bool isLoad;
        private int prevKeyboardLength = 0, currentLayer = 0;
        private Dictionary<int, string> codeToStrEn, codeToStrRu;
        #endregion

        public MainForm()
        {
            InitializeComponent();
            LoadLangDictionary();
            buttons = new List<Button>();
            InitWithCommandLine();
            if (Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName != "en" &&
                Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName != "ru" &&
                Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName != "uk")
                englishToolStripMenuItem_Click(this, new EventArgs());
        }
        private void LocalizeForm()
        {
            var culture = Thread.CurrentThread.CurrentUICulture;
            var formType = GetType();
            string propertyName = "Text";

            foreach (var fieldInfo in formType.GetFields(BindingFlags.NonPublic | BindingFlags.DeclaredOnly | BindingFlags.Instance))
            {
                var propertyInfo = fieldInfo.FieldType.GetProperty(propertyName);
                if (propertyInfo == null)
                    continue;
                object objProperty = resman.GetObject(fieldInfo.Name + '.' + propertyInfo.Name, culture);
                if (objProperty == null) continue;
                object field = fieldInfo.GetValue(this);
                if (field != null)
                    propertyInfo.SetValue(field, objProperty, null);
            }
            var propertyInfo1 = formType.GetProperty(propertyName);
            if (propertyInfo1 == null)
                return;
            object objProperty1 = resman.GetObject("$this." + propertyInfo1.Name, culture);
            if (objProperty1 == null) return;
            propertyInfo1.SetValue(this, objProperty1, null);
        }
        private void GetKeyboardFileType(string path)
        {
            if (Regex.IsMatch(path, VKB_REGEX))
                kbType = KeyboardFileType.VKB;
            else if (Regex.IsMatch(path, SSQ_REGEX))
                kbType = KeyboardFileType.SSQ;
            else if (Regex.IsMatch(path, CN_REGEX))
                kbType = KeyboardFileType.CN;
            else
                kbType = KeyboardFileType.Unknown;
        }
        private void InitWithCommandLine()
        {
            string[] args = Environment.GetCommandLineArgs();
            if (args.Length >= 2)
            {
                if (Path.HasExtension(args[1]) && File.Exists(args[1]))
                    InitWithFile(args[1]);
                else if (!Path.HasExtension(args[1]))
                    InitWithFolder(args[1]);
            }
        }
        private void InitWithFile(string path)
        {
            if (!CheckFile(path)) return;
            GetKeyboardFileType(path);
            this.isLoad = false;

            this.layout = new KeyaboardLayout();
            layout.LayersPosition = new List<int>();
            layout.Layers = new List<Layer>();
            layout.Filepath = path;
            PrepaireKeyboardFile();

            keyboardPlaceholderLabel.Visible = false;
            saveToolStripMenuItem.Enabled = true;
        }
        private void InitWithFolder(string path)
        {
            if (!ScanFoldersForFile(path + "\\")) return;

            DirectoryInfo dir = new DirectoryInfo(this.folderpath);
            codes = new List<int>();

            languageSelectorMenu.Items.Clear();
            //VKB
            foreach (FileInfo f in dir.GetFiles(VKB_FILE, SearchOption.TopDirectoryOnly))
                AddLanguageToMenu(f.Name, " [VKB]");
            //SSQ
            foreach (FileInfo f in dir.GetFiles(SSQ_FILE, SearchOption.TopDirectoryOnly))
                AddLanguageToMenu(f.Name, " [SSQ]");
            //CN
            foreach (FileInfo f in dir.GetFiles(CN_FILE, SearchOption.TopDirectoryOnly))
                AddLanguageToMenu(f.Name, " [CN]");
            if (languageSelectorMenu.Items.Count > 0)
            {
                languageSelectorMenu.Visible = true;
                languageSelectorMenu.SelectedIndex = 0;
            }
        }
        private void AddLanguageToMenu(string fileName, string tag)
        {
            string tmp = Regex.Match(fileName, @"[r_](\d{2,3})").Groups[1].Value;
            int code = Convert.ToInt32(tmp);
            codes.Add(code);
            if (Thread.CurrentThread.CurrentUICulture.ToString() == "en")
                languageSelectorMenu.Items.Add(codeToStrEn[code] + tag);
            else if (Thread.CurrentThread.CurrentUICulture.ToString() == "ru-RU" || Thread.CurrentThread.CurrentUICulture.ToString() == "uk")
                languageSelectorMenu.Items.Add(codeToStrRu[code] + tag);
        }
        private bool CheckFile(string path)
        {
            if (!File.Exists(path))
            {
                MessageBox.Show(string.Format(resman.GetString("fileNotExistMsg"), path));
                return false;
            }
            if (!Path.HasExtension(path))
            {
                MessageBox.Show(resman.GetString("wrongFileMsg"));
                return false;
            }
            return true;
        }
        private bool ScanFoldersForFile(string path)
        {
            if (!Directory.Exists(path))
            {
                MessageBox.Show(string.Format(resman.GetString("dirNotExistMsg"), path));
                return false;
            }
            if (new DirectoryInfo(path).GetFiles(VKB_FILE, SearchOption.TopDirectoryOnly).Length > 0
                || new DirectoryInfo(path).GetFiles(SSQ_FILE, SearchOption.TopDirectoryOnly).Length > 0
                || new DirectoryInfo(path).GetFiles(CN_FILE, SearchOption.TopDirectoryOnly).Length > 0)
            {
                this.folderpath = path;
                return true;
            }
            else
            {
                if (Directory.Exists(path + "\\resource\\plugins\\")
                    && (new DirectoryInfo(path + "\\resource\\plugins\\").GetFiles(VKB_FILE, SearchOption.TopDirectoryOnly).Length > 0
                    || new DirectoryInfo(path + "\\resource\\plugins\\").GetFiles(SSQ_FILE, SearchOption.TopDirectoryOnly).Length > 0
                    || new DirectoryInfo(path + "\\resource\\plugins\\").GetFiles(CN_FILE, SearchOption.TopDirectoryOnly).Length > 0))
                {
                    this.folderpath = path + "\\resource\\plugins\\";
                    return true;
                }
                MessageBox.Show(resman.GetString("fileMissedMsg"));
                return false;
            }
        }
        private void LoadLangDictionary()
        {
            string tmp = string.Empty;
            using (var reader = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream("Nokia_QWERTY_Keyboard_Editor.Resources.LangEN.txt")))
            {
                codeToStrEn = new Dictionary<int, string>();
                while ((tmp = reader.ReadLine()) != null)
                {
                    tmp = tmp.Trim();
                    string[] parts = tmp.Split(' ');
                    int code = Convert.ToInt32(parts[0]);
                    codeToStrEn[code] = parts[1];
                }
            }
            using (var reader = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream("Nokia_QWERTY_Keyboard_Editor.Resources.LangRU.txt")))
            {
                codeToStrRu = new Dictionary<int, string>();
                while ((tmp = reader.ReadLine()) != null)
                {
                    tmp = tmp.Trim();
                    string[] parts = tmp.Split(' ');
                    int code = Convert.ToInt32(parts[0]);
                    codeToStrRu[code] = parts[1];
                }
            }
        }
        private void PrepaireKeyboardFile()
        {
            layout.Data = File.ReadAllBytes(layout.Filepath);           /*Read data*/
            layout.LangCode = layout.Data[31].ToString("D2");           /*Read language code*/
            layout.Lang = ASCIIEncoding.ASCII.GetString(new byte[] { layout.Data[35], layout.Data[36] });
            for (int i = 0; i < layout.Data.Length; i++)                /*Find layouts*/
            {
                if (layout.Data[i] == 0xAB && layout.Data[i - 1] == 0x01 && layout.Data[i - 2] == 0x00 && layout.Data[i - 3] != 0
                    && (layout.Data[i - 3] == (layout.Data[i - 17] * layout.Data[i - 19])))
                {
                    layout.LayersPosition.Add(i + 1);
                }
            }

            int k = 0;
            foreach (int i in layout.LayersPosition)
            {
                int length = layout.Data[layout.LayersPosition[k] - 4];                 /*Read layer length*/
                int rowLength = layout.Data[layout.LayersPosition[k] - 20];             /*Read layer width*/
                int columnLength = layout.Data[layout.LayersPosition[k] - 18];          /*Read layer height*/
                var layerId = (LayerID)layout.Data[layout.LayersPosition[k] - 81];      /*Read layer ID*/

                var symbols = new Symbol[length];
                for (int j = 0, count = 0; !(layout.Data[i + j] == 0x27 && layout.Data[i + j + 1] == 0x27) && count < length; j++)
                {
                    if ((layout.Data[i + j - 1] == 0x01 || layout.Data[i + j - 1] == 0xAB)
                        && layout.Data[i + j + 4] == count / rowLength
                        && layout.Data[i + j + 5] == 0x00
                        && layout.Data[i + j + 6] == count % rowLength
                        && layout.Data[i + j + 7] == 0x00)
                    {
                        var symbol = new byte[] { layout.Data[i + j], layout.Data[i + j + 1] };         /*Read unicode symbol bytes*/
                        symbols[count++] = new Symbol(UnicodeEncoding.Unicode.GetString(symbol), j);    /*Get char & Remember char position*/
                    }
                }
                var l = new Layer(layerId, k, length, rowLength, columnLength, symbols);
                layout.Layers.Add(l);
                k++;
            }

            generalRadio.Checked = true;
            currentLayer = 0;
            if (kbType == KeyboardFileType.CN)
            {
                modesPanel.Visible = false;
                layersSelectListBox.Visible = true;
                layersSelectListBox.Items.Clear();
                layersSelectListBox.Items.AddRange(layout.Layers.ToObjects());
                layersSelectListBox.SelectedIndex = 0;
            }
            else
            {
                modesPanel.Visible = true;
                layersSelectListBox.Visible = false;
                LayersSort(layout.Data[20] == 0x0F);    /*Adapt form to the keyboard layer size (0f-long, 1f-short)*/
            }
            LoadLayer(prevKeyboardLength != layout.Layers[currentLayer].Length ? -1 : 0);           /*Init buttons with layer*/
            isLoad = true;
        }
        private void ApplyChangesToTheKeyboardFile(string filename)
        {
            int k = 0;
            layout.Layers.Sort();
            foreach (int i in layout.LayersPosition)
            {
                var changedSymbols = layout.Layers[k].Symbols.Where(s => s.IsEdited);
                for (int j = 0; j < changedSymbols.Count; j++)
                {
                    byte[] symbol = Encoding.Unicode.GetBytes(changedSymbols[j].Char);
                    int offset = changedSymbols[j].Position;
                    if (layout.Data[i + offset] == layout.Data[i + offset + 20] && layout.Data[i + offset + 1] == layout.Data[i + offset + 21])
                    {
                        layout.Data[i + offset + 20] = symbol[0];
                        layout.Data[i + offset + 21] = symbol[1];
                    }
                    bool secondOverwrite = (layout.Data[i + offset] == layout.Data[i + offset + 2])
                        && (layout.Data[i + offset + 1] == layout.Data[i + offset + 3]);
                    layout.Data[i + offset] = symbol[0];
                    layout.Data[i + offset + 1] = symbol[1];
                    if (secondOverwrite)
                    {
                        layout.Data[i + offset + 2] = symbol[0];
                        layout.Data[i + offset + 3] = symbol[1];
                    }
                }
                k++;
            }
            File.WriteAllBytes(filename, layout.Data);
        }
        private void LayersSort(bool isLong)
        {
            if (!IsNumericLayer(layout.Layers[2].Symbols.Select(s => s.Char).ToArray()) && !IsNumericLayer(layout.Layers[3].Symbols.Select(s => s.Char).ToArray()))
                for (int i = 4, count = 0; i < layout.Layers.Count && count < 2; i++)
                    if (IsNumericLayer(layout.Layers[i].Symbols.Select(s => s.Char).ToArray()))
                        layout.Layers.Swap(++count + 1, i);
            KeyboardAdaptation(isLong);
        }
        private void KeyboardAdaptation(bool isLong)
        {
            ieroTwoRadio.Enabled = isLong;
            ieroTwoBigRadio.Enabled = isLong;
            ieroThreeRadio.Enabled = isLong;
            ieroThreeBigRadio.Enabled = isLong;
        }
        private void GetChanges(int i)
        {
            if (!string.IsNullOrEmpty(buttons[0].Text))
                layout.Layers[currentLayer].Symbols[i] = new Symbol(layout.Layers[currentLayer].Symbols[i], buttons[i].Text);
        }
        private void LoadLayer(int prevLayer)
        {
            var sym = layout.Layers[currentLayer].Symbols;
            if (prevLayer < 0 || layout.Layers[prevLayer].Width != layout.Layers[currentLayer].Width
                || layout.Layers[prevLayer].Height != layout.Layers[currentLayer].Height)
            {
                int prevLength = buttons.Count;
                var diff = layout.Layers[currentLayer].Length - prevLength;
                var absDiff = Math.Abs(diff);
                for (int i = 0; i < absDiff; i++)
                {
                    if (diff > 0)
                    {
                        var b = new Button();
                        b.Size = new Size(32, 32);
                        b.Font = new Font("Arial Unicode MS", 13);
                        b.Margin = new Padding(3);
                        b.Name = string.Format("Button{0}", prevLength + 1 + i);
                        b.Click += OnButtonClick;
                        buttons.Add(b);
                        langSmallKeyboard.Controls.Add(b);
                    }
                    else
                    {
                        buttons.RemoveAt(layout.Layers[currentLayer].Length);
                        langSmallKeyboard.Controls.RemoveAt(layout.Layers[currentLayer].Length + 1);
                    }
                }

                prevKeyboardLength = layout.Layers[currentLayer].Length;
                int width = layout.Layers[currentLayer].Width;
                int height = layout.Layers[currentLayer].Height;
                int leftMargin = (langSmallKeyboard.Width - 38 * width) / 2 + 8;
                int topMargin = (langSmallKeyboard.Height - 38 * height) / 2 + 2;

                for (int i = 0; i < height; i++)
                {
                    for (int j = 0; j < width; j++)
                    {
                        int index = (i * width) + j + 1;
                        buttons[index - 1].Location = new Point(leftMargin + j * 38, topMargin + i * 38);
                        buttons[index - 1].Text = sym[index - 1].Char;
                    }
                }
            }
            else
            {
                for (int i = 0; i < buttons.Count; i++)
                    buttons[i].Text = sym[i].Char;
            }
        }
        private bool IsNumericLayer(string[] data)
        {
            return data.Intersect(new string[] { "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" }).Count == 10;
        }

        private void OnButtonClick(object sender, EventArgs e)
        {
            if (isLoad)
            {
                var b = sender as Button;
                var i = Int32.Parse(Regex.Match(b.Name, @"Button(\d+)").Groups[1].Value) - 1;
                new EditSymbol(b.Text, (s) => { b.Text = s; }).ShowDialog();
                GetChanges(i);
            }
        }
        private void numbersRadio_CheckedChanged(object sender, EventArgs e)
        {
            if (isLoad && numbersRadio.Checked)
            {
                int prevLayer = currentLayer;
                currentLayer = 2;
                LoadLayer(prevLayer);
            }
        }
        private void generalRadio_CheckedChanged(object sender, EventArgs e)
        {
            if (isLoad && generalRadio.Checked)
            {
                int prevLayer = currentLayer;
                currentLayer = 0;
                LoadLayer(prevLayer);
            }
        }
        private void generalBigRadio_CheckedChanged(object sender, EventArgs e)
        {
            if (isLoad && generalBigRadio.Checked)
            {
                int prevLayer = currentLayer;
                currentLayer = 1;
                LoadLayer(prevLayer);
            }
        }
        private void numbersBigRadio_CheckedChanged(object sender, EventArgs e)
        {
            if (isLoad && numbersBigRadio.Checked)
            {
                int prevLayer = currentLayer;
                currentLayer = 3;
                LoadLayer(prevLayer);
            }
        }
        private void ieroOneRadio_CheckedChanged(object sender, EventArgs e)
        {
            if (isLoad && ieroOneRadio.Checked)
            {
                int prevLayer = currentLayer;
                currentLayer = 4;
                LoadLayer(prevLayer);
            }
        }
        private void ieroOneBigRadio_CheckedChanged(object sender, EventArgs e)
        {
            if (isLoad && ieroOneBigRadio.Checked)
            {
                int prevLayer = currentLayer;
                currentLayer = 5;
                LoadLayer(prevLayer);
            }
        }
        private void ieroTwoRadio_CheckedChanged(object sender, EventArgs e)
        {
            if (isLoad && ieroTwoRadio.Checked)
            {
                int prevLayer = currentLayer;
                currentLayer = 6;
                LoadLayer(prevLayer);
            }
        }
        private void ieroTwoBigRadio_CheckedChanged(object sender, EventArgs e)
        {
            if (isLoad && ieroTwoBigRadio.Checked)
            {
                int prevLayer = currentLayer;
                currentLayer = 7;
                LoadLayer(prevLayer);
            }
        }
        private void ieroThreeRadio_CheckedChanged(object sender, EventArgs e)
        {
            if (isLoad && ieroThreeRadio.Checked)
            {
                int prevLayer = currentLayer;
                currentLayer = 8;
                LoadLayer(prevLayer);
            }
        }
        private void ieroThreeBigRadio_CheckedChanged(object sender, EventArgs e)
        {
            if (isLoad && ieroThreeBigRadio.Checked)
            {
                int prevLayer = currentLayer;
                currentLayer = 9;
                LoadLayer(prevLayer);
            }
        }
        private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
        {
            new About().ShowDialog();
        }
        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }
        private void openToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
                InitWithFolder(folderBrowserDialog.SelectedPath);
        }
        private void saveToolStripMenuItem_Click(object sender, EventArgs e)
        {
            saveKeyboardFileDialog.FileName = Path.GetFileName(layout.Filepath);
            if (saveKeyboardFileDialog.ShowDialog() == DialogResult.OK)
                ApplyChangesToTheKeyboardFile(saveKeyboardFileDialog.FileName);
        }
        private void englishToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Thread.CurrentThread.CurrentUICulture = new CultureInfo("en");
            LocalizeForm();
            if (codes != null)
            {
                int index = languageSelectorMenu.SelectedIndex;
                languageSelectorMenu.Items.Clear();
                foreach (int code in codes)
                    languageSelectorMenu.Items.Add(codeToStrEn[code]);
                languageSelectorMenu.SelectedIndex = index;
            }
        }
        private void russianToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Thread.CurrentThread.CurrentUICulture = new CultureInfo("ru");
            LocalizeForm();
            if (codes != null)
            {
                int index = languageSelectorMenu.SelectedIndex;
                languageSelectorMenu.Items.Clear();
                foreach (int code in codes)
                    languageSelectorMenu.Items.Add(codeToStrRu[code]);
                languageSelectorMenu.SelectedIndex = index;
            }
        }
        private void openFileToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (openKeyboardFileDialog.ShowDialog() == DialogResult.OK)
            {
                languageSelectorMenu.Visible = false;
                InitWithFile(openKeyboardFileDialog.FileName);
            }
        }
        private void ukranianToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Thread.CurrentThread.CurrentUICulture = new CultureInfo("uk");
            LocalizeForm();
            if (codes != null)
            {
                int index = languageSelectorMenu.SelectedIndex;
                languageSelectorMenu.Items.Clear();
                foreach (int code in codes)
                    languageSelectorMenu.Items.Add(codeToStrRu[code]);
                languageSelectorMenu.SelectedIndex = index;
            }
        }
        private void languageSelectorMenu_SelectedIndexChanged(object sender, EventArgs e)
        {
            string code = codes[languageSelectorMenu.SelectedIndex].ToString("D2");
            string type = Regex.Match(languageSelectorMenu.SelectedItem.ToString(), @"\[(\w{2,3})\]").Groups[1].Value;
            string file = string.Empty;
            if (type == "VKB")
                file = string.Format(VKB_FORMAT, this.folderpath, code);
            else if (type == "SSQ")
                file = string.Format(SSQ_FORMAT, this.folderpath, code);
            else if (type == "CN")
                file = string.Format(CN_FORMAT, this.folderpath, code);
            else
                MessageBox.Show(resman.GetString("wrongFileMsg"));
            InitWithFile(file);
        }
        private void layersSelectListBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (isLoad)
            {
                int prevLayer = currentLayer;
                currentLayer = layersSelectListBox.SelectedIndex;
                LoadLayer(prevLayer);
            }
        }
    }

    public static class ListExtension
    {
        public static List<U> Select<T, U>(this IList<T> list, Func<T, U> selector)
        {
            var res = new List<U>();
            foreach (var item in list)
                res.Add(selector(item));
            return res;
        }
        public static List<T> Where<T>(this IList<T> list, Func<T, bool> predicate)
        {
            var res = new List<T>();
            foreach (var item in list)
                if (predicate(item))
                    res.Add(item);
            return res;
        }
        public static List<T> Intersect<T>(this IList<T> list, IList<T> otherList)
        {
            var result = new List<T>();
            foreach (T item in list)
                if (otherList.Contains(item))
                    result.Add(item);
            return result;
        }
        public static void Swap<T>(this IList<T> list, int a, int b)
        {
            T first = list[a];
            list[a] = list[b];
            list[b] = first;
        }
        public static object[] ToObjects<T>(this IList<T> list)
        {
            var res = new object[list.Count];
            for (int i = 0; i < list.Count; i++)
                res[i] = list[i];
            return res;
        }
    }

    public delegate U Func<T, U>(T input);
}

namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly)]
    public sealed class ExtensionAttribute : Attribute
    {
    }
}